﻿using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Quail.Helpers
{
    /// <summary>
    /// Generates a selector for a given WebControl
    /// </summary>
    public class SelectorGeneratorFactory
    {
        private readonly List<ISelectorGenerator> _selectorGenerators;

        /// <summary>
        /// Initializes a new instance of the <see cref="SelectorGeneratorFactory"/> class.
        /// </summary>
        public SelectorGeneratorFactory()
        {
            _selectorGenerators = new List<ISelectorGenerator>
                                      {
                                          new IdGenerator(),
                                          new ATagGenerator(),
                                          new MetaGenerator(),
                                          new RadioGroupGenerator(),
                                          new NameGenerator(),
                                          new SrcGenerator(),
                                          new ClassAttributeGenerator(),
                                          new StupidGenerator()
                                      };
        }

        /// <summary>
        /// Interface for selector generators
        /// </summary>
        public interface IGeneratedSelector
        {
            /// <summary>
            /// Gets the generated selector
            /// </summary>
            string Selector { get; }

            /// <summary>
            /// Gets the C# name for the control
            /// </summary>
            string Name { get; }

            /// <summary>
            /// Gets the correct C# type for the control
            /// </summary>
            string CSharpType { get; }

            /// <summary>
            /// Gets the C# method to get the control
            /// </summary>
            string CSharpGetMethod { get; }

            /// <summary>
            /// Gets the C# code for the control
            /// </summary>
            string Code { get; }
        }

        /// <summary>
        /// Interface for selector generators
        /// </summary>
        public interface ISelectorGenerator
        {
            /// <summary>
            /// Determines if the generator can create a selector for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>true if the interface can generate a selector</returns>
            bool CanGenerate(WebControl control);

            /// <summary>
            /// Creates a new instance of the generator specifically for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>the new instance</returns>
            IGeneratedSelector Create(WebControl control);
        }

        /// <summary>
        /// Generates a selector from the control
        /// </summary>
        /// <param name="control">the control</param>
        /// <returns>A selector</returns>
        public IGeneratedSelector GenerateSelector(WebControl control)
        {
            foreach (var generator in _selectorGenerators)
            {
                if (generator.CanGenerate(control))
                {
                    var selector = generator.Create(control);
                    if (selector != null)
                        return selector;
                }
            }

            return null;
        }

        /// <summary>
        /// Gets a list of controls matching the selector on the same page as the passed-in control
        /// </summary>
        /// <param name="control">the control to check</param>
        /// <param name="selector">the selector</param>
        /// <returns>the list of matching controls</returns>
        public static List<WebControl> MatchingControlsOnPage(WebControl control, string selector)
        {
            return control.Page.GetControls<WebControl>(selector);
        }

        /// <summary>
        /// Gets a list of controls matching the selector on the same page as the passed-in control
        /// </summary>
        /// <param name="control">the control to check</param>
        /// <param name="selector">the selector</param>
        /// <returns>the list of matching controls</returns>
        public static List<WebControl> MatchingControlsFromParent(WebControl control, ref string selector)
        {
            if (control.ParentControl is WebControl)
            {
                var parent = ((WebControl) control.ParentControl);
                var result = parent.GetControls<WebControl>(selector);
                selector = parent.Selector + " " + selector;
                return result;
            }

            return new List<WebControl>();
        }


        /*public static string CreateSelectorUsingParentDiv(WebControl control, string selector)
        {
            control.Browser.GetEval()
        }*/

        /// <summary>
        /// Generates a C# name from the control
        /// </summary>
        /// <param name="control">the control</param>
        /// <returns>the suggested name of the control</returns>
        public static string GetCSharpNameFromControl(WebControl control)
        {
            string result = null;
            string innertext = null;

            switch (control.Tag.ToUpper())
            {
                case "A":
                    innertext = control.InnerText;
                    break;
                case "INPUT":
                    switch (control.Attr(KnownAttribute.InputType).ToUpper())
                    {
                        case "BUTTON":
                        case "SUBMIT":
                            innertext = control.InnerText;
                            break;
                    }

                    break;
            }

            if (!string.IsNullOrEmpty(innertext))
            {
                innertext = innertext.Replace(" ", string.Empty);
                innertext = innertext.Replace(".", string.Empty);
                innertext = innertext.Replace(",", string.Empty);
                innertext = innertext.Replace("!", string.Empty);
                innertext = innertext.Replace("#", string.Empty);
                if (innertext.Length >= 5 && innertext.Length <= 20)
                {
                    var regex = new Regex("^[a-zA-Z]+[a-zA-Z0-0]*$");

                    if (regex.IsMatch(innertext))
                        return innertext;
                }
            }

            var id = control.ID;

            if (!string.IsNullOrEmpty(id))
                result = FixDotNetNaming(id);

            if (string.IsNullOrEmpty(result))
            {
                id = control.Name;
                if (!string.IsNullOrEmpty(id))
                    result = FixDotNetNaming(id);
            }

            if (string.IsNullOrEmpty(result))
                result = "[NAME]";
            else
                result = result.Substring(0, 1).ToUpper() + result.Substring(1);

            if (string.Compare(control.Tag, "INPUT", true) == 0 && string.Compare(control.Attr(KnownAttribute.InputType), "HIDDEN", true) == 0)
                result = "Hidden" + result;

            return result;
        }

        /// <summary>
        /// Gets the default C# type for a given web control
        /// </summary>
        /// <param name="control">the control</param>
        /// <returns>the default C# type</returns>
        public static string GetCSharpTypeFromControl(WebControl control)
        {
            switch (control.Tag.ToUpper())
            {
                case "INPUT":
                    switch (control.Attr(KnownAttribute.InputType).ToUpper())
                    {
                        case "TEXT":
                        case "HIDDEN":
                            return "WebInput";
                        case "RADIO":
                            return "WebRadioGroup";
                        case "IMAGE":
                            return "WebImage";
                        case "FILE":
                            return "WebInputFile";
                        case "BUTTON":
                        case "SUBMIT":
                            return "WebButton";
                        case "CHECKBOX":
                            return "WebCheckBox";
                    }

                    break;
                case "A":
                    return "WebLink";
                case "SELECT":
                    return "WebList";
                case "TEXTAREA":
                    return "WebInput";
                case "TABLE":
                    return "WebTable";
            }

            return "WebControl";
        }

        /// <summary>
        /// Determines if the field name is using ASP.Net naming conventions
        /// </summary>
        /// <param name="fieldname">field name to check naming</param>
        /// <returns>true if the control is using ASP.Net naming</returns>
        public static bool IsDotNetNaming(string fieldname)
        {
            return fieldname.Contains("ctl00");
        }

        /// <summary>
        /// Corrects .Net naming conventions
        /// </summary>
        /// <param name="fieldname">field name to fix naming</param>
        /// <returns>the name of the field "cleaned up"</returns>
        public static string FixDotNetNaming(string fieldname)
        {
            if (string.IsNullOrEmpty(fieldname))
                return null;

            var tempField = fieldname.Substring(fieldname.LastIndexOf("_cph") + 1);

            var replaceChar = tempField.IndexOf("$") > 0 ? '$' : '_';

            tempField = tempField.Substring(tempField.LastIndexOf(replaceChar) == -1 ? 0 : tempField.LastIndexOf(replaceChar));
            return tempField;
        }

        /// <summary>
        /// Generates the C# code for a given control
        /// </summary>
        /// <param name="generatedselector">the generated selector</param>
        /// <returns>the c# code</returns>
        public static string GenerateCSharpCode(IGeneratedSelector generatedselector)
        {
            var outCode = "/// <summary>\r\n/// Gets the " + generatedselector.Name + " control\r\n/// </summary>\r\n";

            outCode += string.Format("public {0} {1}\r\n{{\r\n", generatedselector.CSharpType, generatedselector.Name);

            outCode += "\tget\r\n\t{\r\n\t\t";
            outCode += string.Format("return {0}(\"{1}\");\r\n", generatedselector.CSharpGetMethod, generatedselector.Selector);
            outCode += "\t}\r\n";
            outCode += "}";

            return outCode;
        }

        /// <summary>
        /// Simple implementation of the IGeneratedSelector interface
        /// </summary>
        public class ManualGeneratedSelector : IGeneratedSelector
        {
            /// <summary>
            /// Gets or sets the generated selector
            /// </summary>
            public string Selector
            {
                get; set;
            }

            /// <summary>
            /// Gets or sets the C# name for the control
            /// </summary>
            public string Name
            {
                get; set;
            }

            /// <summary>
            /// Gets or sets the correct C# type for the control
            /// </summary>
            public string CSharpType
            {
                get; set;
            }

            /// <summary>
            /// Gets or sets the C# method to get the control
            /// </summary>
            public string CSharpGetMethod
            {
                get; set;
            }

            /// <summary>
            /// Gets the C# code for the control
            /// </summary>
            public string Code
            {
                get
                {
                    return GenerateCSharpCode(this);
                }
            }
        }

        /// <summary>
        /// Simple implementation of the IGeneratedSelector interface
        /// </summary>
        public class SimpleGeneratedSelector : ManualGeneratedSelector
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="SimpleGeneratedSelector"/> class.
            /// </summary>
            /// <param name="selector">The jQuery selector.</param>
            /// <param name="control">The control.</param>
            public SimpleGeneratedSelector(string selector, WebControl control)
            {
                Selector = selector;
                CSharpType = GetCSharpTypeFromControl(control);
                Name = GetCSharpNameFromControl(control);

                CSharpGetMethod = "Get" + CSharpType.Substring(3);
            }
        }

        /// <summary>
        /// Simple implementation of the IGeneratedSelector interface
        /// </summary>
        public class SimpleListGeneratedSelector : ManualGeneratedSelector
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="SimpleListGeneratedSelector"/> class.
            /// </summary>
            /// <param name="selector">The jQuery selector.</param>
            /// <param name="control">The control.</param>
            public SimpleListGeneratedSelector(string selector, WebControl control)
            {
                Selector = selector;
                var cstype = GetCSharpTypeFromControl(control) ;
                Name = GetCSharpNameFromControl(control);

                CSharpGetMethod = "Get" + cstype.Substring(3) + "List";
                CSharpType = "List<" + cstype + ">";
            }
        }

        /// <summary>
        /// Default generator for default selectors
        /// </summary>
        private class StupidGenerator : ISelectorGenerator
        {
            /// <summary>
            /// Determines if the generator can create a selector for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>true if the interface can generate a selector</returns>
            public bool CanGenerate(WebControl control)
            {
                return true;
            }

            /// <summary>
            /// Creates a new instance of the generator specifically for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>the new instance</returns>
            public IGeneratedSelector Create(WebControl control)
            {
                return new SimpleGeneratedSelector(control.Selector, control);
            }
        }

        /// <summary>
        /// Generator for controls with IDs
        /// </summary>
        private class IdGenerator : ISelectorGenerator
        {
            /// <summary>
            /// Determines if the generator can create a selector for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>true if the interface can generate a selector</returns>
            public bool CanGenerate(WebControl control)
            {
                return !string.IsNullOrEmpty(control.ID);
            }

            /// <summary>
            /// Creates a new instance of the generator specifically for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>the new instance</returns>
            public IGeneratedSelector Create(WebControl control)
            {
                // try based on id
                var id = control.ID;
                if (string.IsNullOrEmpty(id))
                    return null;

                string selector;

                if (IsDotNetNaming(id))
                {
                    selector = control.Tag + "[id$=" + FixDotNetNaming(id) + "]";
                }
                else
                    selector = "#" + id;

                
                var foundControls = MatchingControlsOnPage(control, selector);
                if (foundControls.Count != 1)
                {
                    if (IsDotNetNaming(id))
                    {
                        selector = "#" + id;
                        foundControls = control.Page.GetControls<WebControl>(selector);
                    }
                }

                if (foundControls.Count == 1)
                    return new SimpleGeneratedSelector(selector, control);

                foundControls = MatchingControlsFromParent(control, ref selector);
                if (foundControls.Count == 1)
                    return new SimpleGeneratedSelector(selector, control);

                return null;
            }
        }

        private class NameGenerator : ISelectorGenerator
        {
            public bool CanGenerate(WebControl control)
            {
                return !string.IsNullOrEmpty(control.Name);
            }

            public IGeneratedSelector Create(WebControl control)
            {
                // try based on Name
                var name = control.Name;
                if (string.IsNullOrEmpty(name))
                    return null;

                var selector = control.Tag.ToLower();
                
                if (string.Compare(selector, "input", true) == 0)
                    selector += ":" + control.Attr(KnownAttribute.InputType);
                   
                selector += "[name=" + name + "]";

                var foundControls = MatchingControlsOnPage(control, selector);
                if (foundControls.Count == 1)
                    return new SimpleGeneratedSelector(foundControls[0].Selector, control);

                if (foundControls.Count > 1)
                    return new SimpleListGeneratedSelector(selector, control);

                return null;
            }
        }

        /// <summary>
        /// Generates a selector for an A tag
        /// </summary>
        private class ATagGenerator : ISelectorGenerator
        {
            /// <summary>
            /// Determines if the generator can create a selector for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>true if the interface can generate a selector</returns>
            public bool CanGenerate(WebControl control)
            {
                return control.Tag.ToUpper() == "A";
            }

            /// <summary>
            /// Creates a new instance of the generator specifically for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>the new instance</returns>
            public IGeneratedSelector Create(WebControl control)
            {
                var selector = string.Empty;
                var innertext = control.InnerText;

                if (innertext.Length > 3 && innertext.Length < 15)
                {
                    selector = "a:linkbytext(" + innertext + ")";
                    if (MatchingControlsOnPage(control, selector).Count == 1)
                    {
                        var result = new SimpleGeneratedSelector(innertext, control)
                                         {
                                             CSharpGetMethod = "GetLinkByText"
                                         };

                        return result;
                    }
                }

                var href = control.Attr(KnownAttribute.Href);
                if (href != string.Empty && href != "#")
                    selector = "a[href='" + control.Attr(KnownAttribute.Href) + "']";

                return MatchingControlsOnPage(control, selector).Count == 1
                    ? new SimpleGeneratedSelector(selector, control)
                    : null;
            }
        }

        /// <summary>
        /// Generates a selector for an INPUT RADIO group
        /// </summary>
        private class RadioGroupGenerator : ISelectorGenerator
        {
            /// <summary>
            /// Determines if the generator can create a selector for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>true if the interface can generate a selector</returns>
            public bool CanGenerate(WebControl control)
            {
                return control.Tag.ToUpper() == "INPUT" && control.Attr(KnownAttribute.InputType).ToUpper() == "RADIO";
            }

            /// <summary>
            /// Creates a new instance of the generator specifically for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>the new instance</returns>
            public IGeneratedSelector Create(WebControl control)
            {
                var selector = "input:radio[name$=" + FixDotNetNaming(control.Name) + "]";

                return new SimpleGeneratedSelector(selector, control);
            }
        }

        /// <summary>
        /// Generates a selector on controls that have src attribute
        /// </summary>
        private class SrcGenerator : ISelectorGenerator
        {
            /// <summary>
            /// Determines if the generator can create a selector for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>true if the interface can generate a selector</returns>
            public bool CanGenerate(WebControl control)
            {
                return !string.IsNullOrEmpty(control.Attr(KnownAttribute.Src));
            }

            /// <summary>
            /// Creates a new instance of the generator specifically for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>the new instance</returns>
            public IGeneratedSelector Create(WebControl control)
            {
                var selector = control.Tag.ToUpper() == "INPUT" && control.Attr(KnownAttribute.InputType).ToUpper() == "IMAGE" ? "input:image" : control.Tag.ToLower();

                var src = control.Attr(KnownAttribute.Src);

                if (src.LastIndexOf('/') > 0)
                    selector += "[src$=" + src.Substring(src.LastIndexOf('/') + 1) + "]";
                else
                    selector += "[src=" + src + "]";

                return MatchingControlsOnPage(control, selector).Count == 1
                    ? new SimpleGeneratedSelector(selector, control)
                    : null;
            }
        }

        /// <summary>
        /// Generates a selector if the control has a class attribute
        /// </summary>
        private class ClassAttributeGenerator : ISelectorGenerator
        {
            /// <summary>
            /// Determines if the generator can create a selector for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>true if the interface can generate a selector</returns>
            public bool CanGenerate(WebControl control)
            {
                return !string.IsNullOrEmpty(control.Attr(KnownAttribute.ClassName));
            }

            /// <summary>
            /// Creates a new instance of the generator specifically for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>the new instance</returns>
            public IGeneratedSelector Create(WebControl control)
            {
                var selector = control.Tag.ToLower() + "." + control.Attr(KnownAttribute.ClassName);

                return MatchingControlsOnPage(control, selector).Count == 1
                    ? new SimpleGeneratedSelector(selector, control)
                    : null;
            }
        }

        /// <summary>
        /// Generates a selector if the control has a class attribute
        /// </summary>
        private class MetaGenerator : ISelectorGenerator
        {
            /// <summary>
            /// Determines if the generator can create a selector for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>true if the interface can generate a selector</returns>
            public bool CanGenerate(WebControl control)
            {
                return control.Tag.ToUpper() == "META";
            }

            /// <summary>
            /// Creates a new instance of the generator specifically for the control
            /// </summary>
            /// <param name="control">the control to generate</param>
            /// <returns>the new instance</returns>
            public IGeneratedSelector Create(WebControl control)
            {
                string selector = null;

                var attr = control.CustomAttr("http-equiv");
                if (!string.IsNullOrEmpty(attr))
                    selector = "meta" + "[http-equiv=" + control.CustomAttr("http-equiv") + "]";

                return selector != null && MatchingControlsOnPage(control, selector).Count == 1
                    ? new SimpleGeneratedSelector(selector, control)
                    : null;
            }
        }
    }
}
